package csel.model;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
import org.apache.log4j.Logger;

import csel.model.entity.*;

public class Model implements Invoker, Observable<Object>
{
	/* OBSERVABLE */
	
	private final ObsHelper<Object> observers;
	
	/* LOGGING */
	
	private final static Logger logger = Logger.getLogger("csel.model");
	
	/* GAME DATA */
	
	private final LoaderSaver loaderSaver;
	
	private volatile GameData gameData;          // needs to be volatile?
	private final List<Occupation> occupations;
	
	public Model()
	{
		observers = new ObsHelper<Object>(this);
		occupations = new ArrayList<Occupation>(3);

		occupations.add(new Smasher());
		occupations.add(new Summoner());
		occupations.add(new Sneak());
		
		loaderSaver = new SerializationLoaderSaver();
	}

	public void accept(GameCommand gc)
	{
		logger.debug(String.format("Model got command: %s", gc));
		
		gc.execute();
	}
	
	public void load(String filename) throws IOException, FileNotFoundException
	{
		if (gameData != null)
			gameData.stopGame();
		
		gameData = loaderSaver.load(filename);
		gameData.startGame();
		
		notifyO();
	}

	/**
	 * Save the game to the specified path.
	 * @throws java.io.FileNotFoundException if the file cannot be created
	 * @throws java.io.IOException if a write error occurred
	 */
	public void save(String filename) throws IOException, FileNotFoundException
	{
		loaderSaver.save(filename, gameData);
		
		logger.info(String.format("Game saved to %s.", filename));
	}
	
	public void stopGame()
	{
		if (gameData != null)
			gameData.stopGame();
	}
	
	public PlayerCharacter getPlayerCharacter()
	{
		return (gameData != null) ? gameData.getPlayerCharacter() : null;
	}
	
	/**
	 * @return the current map in use, or null if a game is not in progress
	 */
	public Map getMap()
	{
		return (gameData != null) ? gameData.getMap() : null;
	}
	
	public List<Occupation> getAllOccupations()
	{
		return this.occupations;
	}
	
	void setGameData(GameData gd)
	{
		gameData = gd;
		
		notifyO();
	}
	
	/* OBSERVABLE */

	public void addObserver(Observer<Object> o)
	{
		observers.add(o);
	}

	public boolean removeObserver(Observer<Object> o)
	{
		return observers.remove(o);
	}
	
	protected void notifyO()
	{
		observers.notifyO(null);
	}
}
